use std::path::Path;
use std::io::Read;
use std::io::BufReader;
use std::fs::File;
use std::io::BufRead;
use std::io;
use dirs::home_dir;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use reqwest::Client;
use serde_json::Value;
use threadpool::ThreadPool;
use std::fmt;
use std::str::FromStr;
// use std::io::Read;
use rusqlite::Connection;
use rusqlite::params;
use colored::Colorize;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;

type O<T> =  Result<T, Box<dyn std::error::Error>>;
#[allow(non_upper_case_globals)]
const PLow:i32 = 401;
#[allow(non_upper_case_globals,dead_code)]
const PHeight:i32 =402;
#[allow(non_upper_case_globals,dead_code)]
const New_Jersey:i32 = 1;
#[allow(non_upper_case_globals,dead_code)]
const Chicago : i32 = 2;
#[allow(non_upper_case_globals,dead_code)]
const Dallas : i32 = 3;
#[allow(non_upper_case_globals,dead_code)]
const Seattle : i32 =4 ;
#[allow(non_upper_case_globals,dead_code)]
const Los_Angeles : i32 = 5;
#[allow(non_upper_case_globals,dead_code)]
const Atlanta : i32 = 6;
#[allow(non_upper_case_globals,dead_code)]
const Amsterdam : i32 = 7;
#[allow(non_upper_case_globals,dead_code)]
const London : i32 = 8;
#[allow(non_upper_case_globals,dead_code)]
const Frankfurt : i32 = 9;
#[allow(non_upper_case_globals,dead_code)]
const Silicon_Valley : i32 = 12;
#[allow(non_upper_case_globals,dead_code)]
const Sydney : i32 = 19;
#[allow(non_upper_case_globals,dead_code)]
const Paris : i32 = 24;
#[allow(non_upper_case_globals,dead_code)]
const Miami : i32 = 39;
#[allow(non_upper_case_globals,dead_code)]
const Singapore : i32 = 40;
#[allow(non_upper_case_globals,dead_code)]
const Tokyo : i32 = 25    ;



#[derive(Serialize, Deserialize)]
pub struct Vps{
    os:String,
    main_ip:String,
    default_password:String,
    status:String,
    tag:String,
    location:String,
    label:String,
}

#[allow(unused)]
struct Host {
    host:String,
    port:i32,
    user:String, 
    passwd:String,
}




#[allow(unused)]
struct CreataServer{
    region:i32,
    plan:i32,
    os:i32,
}
impl Default for CreataServer{
    fn default()-> Self{
        Self{
            os:270,
            region: Tokyo,
            plan:PLow
        }
    }
}
impl fmt::Display for CreataServer{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "region:{} plan:{} os:{}", self.region, self.plan, self.os)
    }
}

#[allow(unused)]
impl CreataServer {
    fn create_server(self, db_path:&str, tag:&str) -> O<()>{
        let cli = Client::new();
        let mut p = db_path.to_string();
    
        if p.starts_with("~/"){ //
            let home = home_dir().unwrap().to_str().unwrap().to_string();
            p = p.replace("~", &home);
        }
        
        let fomrdata = [("DCID", format!("{}",self.region)), ("VPSPLANID", format!("{}",self.plan)), ("OSID", format!("{}", self.os)),("tag", String::from(tag)),("label", String::from(tag)) ];
        let key = get_user(&p).expect("get api key error");
        let res:HashMap<String,Value> = cli.post("https://api.vultr.com/v1/server/create")
            .header("API-Key", key)
            .form(&fomrdata)
            .send().expect("send error!")
            .json().expect("json error!");
        Ok(())
    }

}



pub fn list_servers(db_path:&str){
    let cli = Client::new();
    let mut p = db_path.to_string();

    if p.starts_with("~/"){ //
        let home = home_dir().unwrap().to_str().unwrap().to_string();
        p = p.replace("~", &home);
    }
    
    let key = get_user(&p).expect("get api key error");
    let res:HashMap<String,Value> = cli.get("https://api.vultr.com/v1/server/list")
        .header("API-Key", key)
        .send().expect("send error!")
        .json().expect("json error!");
    for (_,v) in res{
        let pre = if let Some(status) = v.get("power_status").unwrap().as_str(){
            if status == "running"{
                "[●]".green().to_string()
            }else{
                "[○]".yellow().to_string()
            }
        }else{
            "[○]".yellow().to_string()
        };
        println!("{} ip: {} [{}] loc:{} tag:{}",pre, v.get("gateway_v4").unwrap().as_str().unwrap().green(),v.get("date_created").unwrap().as_str().unwrap(), v.get("location").unwrap().as_str().unwrap(),v.get("tag").unwrap().as_str().unwrap().yellow())
    }
}

#[allow(unused)]
pub fn create_by_file(db_path:&str,file_name: &str, tag:&str){

    let s = [New_Jersey,Chicago,Dallas,Seattle,Los_Angeles,Atlanta,Amsterdam,London,Frankfurt,Silicon_Valley,Sydney,Paris,Miami,Singapore,Tokyo];
    let s_key = ["Us","Us","Us","Us","Us","Us","Eu","Eu","Eu","Us","Au","Eu","Us","As","As"];
    let s_key2 = ["New_Jersey","Chicago","Dallas","Seattle","Los_Angeles","Atlanta","Amsterdam","London","Frankfurt","Silicon_Valley","Sydney","Paris","Miami","Singapore","Tokyo"];
    
    let mut s_map: HashMap<&str,i32> = HashMap::new();
    s_key.iter().enumerate().map(|(no,v)| {
        s_map.insert(v, s[no]);
    }).collect::<Vec<_>>();
    
    let mut num : u64 = 0;
    let mut get_ids:Vec<i32> = Vec::new();
    
    match std::fs::File::open(file_name){
        Ok(mut fp) => {
            println!("open success {}",file_name);
            
            let mut reader = BufReader::new(&fp);
            for line in reader.lines(){
                match line{
                    Ok(l) => {
                        if l.contains(":") {
                            // let l =line.unwrap();
                            let fs:Vec<&str> = l.splitn(2,":").collect();
                            let name =  fs[0].trim();
                            let numStr = fs[1].trim();
                            if name == "world"{
                                s_key2.iter().enumerate().map(|(no, v)| {
                                    let i = i32::from_str(numStr).ok().unwrap();
                                    
                                    for i in [0..i].iter(){
                                        println!("World : {}=>{}",v.to_string().to_lowercase(),1);
                                        num+=1;
                                        get_ids.push(no as i32);
                                    }
                                }).collect::<Vec<_>>();
                                continue
                            }
                            s_key.iter().enumerate().map(|(no, v)| { if v.to_string().to_lowercase() == name.to_lowercase(){ 
                                let i = i32::from_str(numStr).ok().unwrap();
                                for i in [0..i].iter(){
                                    println!("Country :{}=>{}",s_key2[no].to_lowercase(),1);
                                    num+=1;
                                    get_ids.push(no as i32);
                                }
                                
                            }}).collect::<Vec<_>>();
                            if get_ids.len() == 0{
                                s_key2.iter().enumerate().map(|(no, v)| { if v.to_lowercase() == name.to_lowercase(){ 
                                    let i = i32::from_str(numStr).ok().unwrap();
                                    for i in [0..i].iter(){
                                        println!("City: {}=>{}",s_key2[no].to_string().to_lowercase(),1);
                                    
                                        num+=1;
                                        get_ids.push(no as i32);
                                    }
                                }}).collect::<Vec<_>>();
                            }
                        }
                    },
                    Err(e) =>{

                    }
                }
                
            }

            let ppb = ProgressBar::new(num as u64);
            let pool = ThreadPool::new(8);
            println!("len {}",get_ids.len());
            if get_ids.len()> 0{
                println!("try to create ....");
                ppb.set_style(ProgressStyle::default_bar()
                .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta})")
                .progress_chars("#>-"));
                let (sx, rx) = std::sync::mpsc::channel::<String>();
                let ll = get_ids.len();
                for i in 0..num{
                    let path =  db_path.to_string();
                    let tag = tag.to_string();
                    let sx_shared = sx.clone();
                    let id = s[get_ids[(i as usize) % ll] as usize];
                    pool.execute(move||{
                        let mut cs = CreataServer::default();
                        cs.region = id;
                        println!("{}", cs);
                        match cs.create_server(&path, &format!("{}-{}",&tag,i)){
                            Ok(()) => {
                                sx_shared.send("Ok".to_string()).unwrap();
                            },
                            Err(e) => {
                                sx_shared.send(format!("{}", e)).unwrap();
                            }
                        }
                    });
                }
                for _ in 0..num{
                    let res = rx.recv().unwrap();
                    if res !=  "Ok" {
                        ppb.println(res.red().to_string());
                    }
                    ppb.inc(1);
                    
                }
            }
        },
        Err(e) => {
            println!("err : {} ", e);
        },
    }
}

#[allow(unused)]
pub fn create_country(db_path:&str, country:&str, tag:&str, num:i32){
    let s = [New_Jersey,Chicago,Dallas,Seattle,Los_Angeles,Atlanta,Amsterdam,London,Frankfurt,Silicon_Valley,Sydney,Paris,Miami,Singapore,Tokyo];
    let s_key = ["Us","Us","Us","Us","Us","Us","Eu","Eu","Eu","Us","Au","Eu","Us","As","As"];
    let s_key2 = ["New_Jersey","Chicago","Dallas","Seattle","Los_Angeles","Atlanta","Amsterdam","London","Frankfurt","Silicon_Valley","Sydney","Paris","Miami","Singapore","Tokyo"];
    
    let mut s_map: HashMap<&str,i32> = HashMap::new();
    s_key.iter().enumerate().map(|(no,v)| {
        s_map.insert(v, s[no]);
    }).collect::<Vec<_>>();
    let pool = ThreadPool::new(8);
    let ppb = ProgressBar::new(num as u64);
    let mut get_ids:Vec<i32> = Vec::new();
    s_key.iter().enumerate().map(|(no, v)| { if v == &country{ get_ids.push(no as i32)}}).collect::<Vec<_>>();
    if get_ids.len() == 0{
        s_key2.iter().enumerate().map(|(no, v)| { if v == &country{ get_ids.push(no as i32)}}).collect::<Vec<_>>();
    }
    if get_ids.len()> 0{
        ppb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta})")
        .progress_chars("#>-"));
        let (sx, rx) = std::sync::mpsc::channel::<String>();
        let ll = get_ids.len();
        for i in 0..num{
            let path =  db_path.to_string();
            let tag = tag.to_string();
            let sx_shared = sx.clone();
            let id = s[get_ids[(i as usize) % ll] as usize];
            pool.execute(move||{
                let mut cs = CreataServer::default();
                cs.region = id;
                println!("{}", cs);
                match cs.create_server(&path, &format!("{}-{}",&tag,i)){
                    Ok(()) => {
                        sx_shared.send("Ok".to_string()).unwrap();
                    },
                    Err(e) => {
                        sx_shared.send(format!("{}", e)).unwrap();
                    }
                }
            });
        }
        for _ in 0..num{
            let res = rx.recv().unwrap();
            if res !=  "Ok" {
                ppb.println(res.red().to_string());
            }
            ppb.inc(1);
            
        }
    }
    
}


#[allow(unused)]
pub fn create_world(db_path:&str, tag:&str){
    let s = [New_Jersey,Chicago,Dallas,Seattle,Los_Angeles,Atlanta,Amsterdam,London,Frankfurt,Silicon_Valley,Sydney,Paris,Miami,Singapore,Tokyo];
    let pool = ThreadPool::new(8);
    let ppb = ProgressBar::new(15);
    ppb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta})")
        .progress_chars("#>-"));
    let (sx, rx) = std::sync::mpsc::channel::<String>();
    for i in 0..15{
        let path =  db_path.to_string();
        let tag = tag.to_string();
        let sx_shared = sx.clone();
        pool.execute(move||{
            let mut cs = CreataServer::default();
            cs.region = s[i];
            // println!("{}", cs);
            match cs.create_server(&path, &format!("{}-{}",&tag,i)){
                Ok(()) => {
                    sx_shared.send("Ok".to_string()).unwrap();
                },
                Err(e) => {
                    sx_shared.send(format!("{}", e)).unwrap();
                }
            }
        });
    }
    for _ in 0..15{
        let res = rx.recv().unwrap();
        if res !=  "Ok" {
            ppb.println(res.red().to_string());
        }
        ppb.inc(1);
        
    }
}

trait Search{
    fn search(self, key:Option<&str>) -> bool;
}

impl Search for &Vec<String>{
    fn search(self, key:Option<&str>) -> bool{
        if let Some(kk) = key{
            for s in self{
                if kk.contains(s){
                    return true
                }
            }
        }
        false
    }
}

#[allow(unused)]
pub fn remove_vps_by_tag(db_path:&str, tag:&str, filter_tags:&Vec<String>) -> O<()>{
    if tag.len() < 3{
        println!("{}","tag filter string must more than 3 !!!".red().bold());
        return Ok(())
    }
    let cli = Client::new();
    let mut p = db_path.to_string();

    if p.starts_with("~/"){ //
        let home = home_dir().unwrap().to_str().unwrap().to_string();
        p = p.replace("~", &home);
    }
    println!("->{}", p.yellow());
    let key = get_user(&p)?;

    let res:HashMap<String,Value> = cli.get("https://api.vultr.com/v1/server/list")
            .header("API-Key", &key)
            .send()?
            .json()?;
    let mut ids:Vec<String> = Vec::new() ;
    let (section, value) = if tag.contains(":"){
        let p:Vec<String> = tag.splitn(2, ":").map(|x|x.to_string()).collect::<Vec<String>>();
        (p[0].clone(), p[1].clone())
    }else{
        ("tag".to_string(), tag.to_string())
    };

    println!("server list  count:{}, filter:{} seciton:{}", res.len(), value.green(), section.yellow());
    
    
    if section == "tag"{
        let _ = res.iter().map(|(k,v)| {
            if filter_tags.search(v.get("tag").unwrap().as_str()){
                
            }else if v.get("tag").unwrap().as_str().unwrap().contains(&value){
                ids.push(v.get("SUBID").unwrap().as_str().unwrap().to_string());
                println!("{} : ip:{:?}  loc:{:?} tag:{:?}", "[x]".red(), v.get("gateway_v4").unwrap().as_str().unwrap(), v.get("location").unwrap().as_str().unwrap(), v.get("tag").unwrap().as_str().unwrap());
            }
        }).collect::<Vec<_>>();
    }else{
        for (k,v ) in res{
            
            if let Some(vv) = v.get("tag"){
                if filter_tags.search(vv.as_str()){
                    continue
                }
            }
            
            if let Some(vv) = v.get(&section.trim()){
                if vv.as_str().unwrap().contains(&value.trim()){
                    if let Some(subid) = v.get("SUBID"){
                        ids.push(subid.as_str().unwrap().to_string());
                        println!("{} : ip:{:?}  loc:{:?} tag:{:?}", "[x]".red(), v.get("gateway_v4").unwrap().as_str().unwrap(), v.get("location").unwrap().as_str().unwrap(), v.get("tag").unwrap().as_str().unwrap());
                    }
                    
                }else{
                    // println!("filter {}:{}", section.green(), v.to_string().yellow().bold());
                }
            }
        }
    }
    
    
    if ids.len() == 0 {
        return Ok(());
    }

    let (sx, rx) = std::sync::mpsc::channel::<i32>();
    let pool = ThreadPool::new(8);
    let ppb = ProgressBar::new(ids.len() as u64);
    ppb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta})")
        .progress_chars("#>-"));

    for id in ids.clone(){
        let key = key.clone();
        let sx = sx.clone();
        pool.execute(move || {
            let cli = Client::new();
            let res = cli.post("https://api.vultr.com/v1/server/destroy")
                .header("API-Key", key)
                .form(&[("SUBID", id)])
                .send();
            sx.send(1).expect("channel closed");
        });
    }
    for _ in ids{
        ppb.inc(1);
        rx.recv().unwrap();
    }
    if let Ok(hs) = pull_all_hosts(db_path){
        let conn = Connection::open(&p)?;
        conn.execute(
            "DELETE FROM Host;",
            params![],
        )?;
        for h in &hs{
            conn.execute(
                "INSERT INTO Host (user, host, port, passwd, tag, location)
                          VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
                params!["root", h.main_ip, 22, h.default_password, h.label, h.location],
            )?;
            
        }
        println!("all vps : {}", hs.len());
    }
    Ok(())
}


#[allow(dead_code)]
fn read_lien() -> String{
    let mut buf = String::new();
    println!("api key: {}","".underline());
    let _ = io::stdin().read_line(&mut buf);
    buf.trim().to_string()
}

#[allow(unused)]
fn pull_all_hosts(db_path:&str) -> O<Vec<Vps>>{
    //curl -H 'API-Key: YOURKEY' https://api.vultr.com/v1/server/list
    let mut hosts:Vec<Vps> = Vec::new();
    let cli = Client::new();
    let mut p = db_path.to_string();
    
    if p.starts_with("~/"){ //
        let home = home_dir().unwrap().to_str().unwrap().to_string();
        p = p.replace("~", &home);
    }
    println!("->{}", p.yellow());
    let key = get_user(&p)?;
    let res:HashMap<String,Value> = cli.get("https://api.vultr.com/v1/server/list")
        .header("API-Key", &key)
        .send()?
        .json()?;
    for (k,vs) in res{
        // println!("id:{}", k.yellow());
        // let vs = v.unwrap();
        let host:Vps = serde_json::from_value(vs)?; 
        // let host:Vps = Vps{
        //     os: vs.get("os").unwrap().as_str().unwrap().to_string(),
        //     main_ip: vs.get("main_ip").unwrap().as_str().unwrap().to_string(),
        //     default_password: vs.get("default_password").unwrap().as_str().unwrap().to_string(),
        //     status: vs.get("status").unwrap().as_str().unwrap().to_string(),
        //     tag: vs.get("tag").unwrap().as_str().unwrap().to_string(),
        // };
        hosts.push(host);
    }
    Ok(hosts)
}

pub fn set_user(db_path:&str, key:&str) -> O<()>{
    let mut p = db_path.to_string();
    
    if p.starts_with("~/"){ //
        let home = home_dir().unwrap().to_str().unwrap().to_string();
        p = p.replace("~", &home);
    }
    let pp = Path::new(&p);
    if !pp.parent().unwrap().exists(){
        std::fs::create_dir(&pp)?;
    }
    if !pp.exists(){
        
        let conn = Connection::open(&p)?;
        log::info!("crate table:{} | {}", "Host, User".bold(), key);
        conn.execute(
            r#"CREATE TABLE User(api TEXT NOT NULL);"#
        ,params![])?;
        conn.execute(
            r#"CREATE TABLE Host (
                      host            TEXT NOT NULL,
                      passwd          TEXT NOT NULL,
                      port            Integer default 22,
                      user            TEXT DEFAULT "root",
                      location        TEXT DEFAULT "x",
                      tag             TEXT NOT NULL
                      );"#,
            params![],
        )?;
        conn.execute(
            "INSERT INTO User (api)
                      VALUES (?1)",
            params![key],
        )?;
        
    }
    
    let conn = Connection::open(&p)?;
    conn.execute(
        "DELETE FROM User;",
        params![],
    )?;
    conn.execute(
        "INSERT INTO User (api)
                  VALUES (?1)",
        params![key],
    )?;
    Ok(())
}

fn get_user(db_path: &str) -> O<String>{
    let mut key = String::new();
    let conn =  Connection::open(db_path)?;
    let mut smt = conn.prepare("SELECT api from User;")?;
    let mut rows = smt.query(params![])?;
    let mut c = 0;
    while let Some(api) = rows.next()? {
        key = api.get(0)?;
        // key.push_str();
        c+=1;
    }
    if key.len() == 0 || c ==0{
        let key = read_lien();
        let _ = set_user(db_path, &key)?;
    }
    Ok(key)
}

pub fn pull_to_db(db_path:&str) ->O<()>{
    let mut p = db_path.to_string();
    
    if p.starts_with("~/"){ //
        let home = home_dir().unwrap().to_str().unwrap().to_string();
        p = p.replace("~", &home);
    }
    if !Path::new(&p).exists() {
        let conn = Connection::open(&p)?;
        
        let pp = Path::new(&p);
        if !pp.parent().unwrap().exists(){
            std::fs::create_dir(&pp)?;
        }
        conn.execute(
            r#"CREATE TABLE User(api TEXT NOT NULL);"#
        ,params![])?;
        
        let key = read_lien();
        conn.execute(
            r#"CREATE TABLE Host (
                      host            TEXT NOT NULL,
                      passwd          TEXT NOT NULL,
                      port            Integer default 22,
                      user            TEXT DEFAULT "root",
                      location        TEXT DEFAULT "x",
                      tag             TEXT NOT NULL
                      );"#,
            params![],
        )?;
        set_user(db_path,&key)?;
    }
    
        
    if let Ok(hs) = pull_all_hosts(db_path){
        let conn = Connection::open(&p)?;
        conn.execute(
            "DELETE FROM Host;",
            params![],
        )?;
        for h in &hs{
            conn.execute(
                "INSERT INTO Host (user, host, port, passwd, tag, location)
                          VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
                params!["root", h.main_ip, 22, h.default_password, h.label, h.location],
            )?;
            
        }
        println!("all vps: {}", hs.len());
    }
    Ok(())
}

